#pragma warning(disable : 4786)

#include "particleSystem.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include "modelerapp.h"

/***************
 * Constructors
 ***************/

ParticleSystem::ParticleSystem() 
{
	// TODO: this should not be in the ctor
	gravity = new ConstantGravitationalField(GRAVITY_ACCELERATION);
	forceList.push_back(gravity);	

	viscousDrag = new DragForce(DRAG_COEFF_AIR);
	forceList.push_back(viscousDrag);

	sparkleSpring = new SpringForce(SPARKLE_SPRING_CONSTANT, SPARKLE_SPRING_DAMPING);
	forceList.push_back(sparkleSpring);
	
	earSpring = new SpringForce(EAR_SPRING_CONSTANT, EAR_SPRING_DAMPING);
	forceList.push_back(earSpring);

	earStabilizer = new ConstantGravitationalField(EAR_STABILIZER_GRAVITY);
	forceList.push_back(earStabilizer);	

	earDrag = new DragForce(DRAG_COEFF_EAR);
	forceList.push_back(earDrag);

	clearBaked();
}

/*************
 * Destructor
 *************/

ParticleSystem::~ParticleSystem() 
{
	// TODO: this should be in some other place, NOT the destructor, prob somewhere in start/stop/reset simulation

	cout << "Particle Destructor ... " << endl;

	// Destroy forces
	for (vector<Force*>::iterator i = forceList.begin(); i != forceList.end(); i++) {
		delete (*i);
	}

	// Destroy objects
	for (vector<ParticleObject*>::iterator i = objectList.begin(); i != objectList.end(); i++) {
		delete (*i);
	}

	// Destroy particles
	for (vector<Particle*>::iterator i = particleList.begin(); i != particleList.end(); i++) {
		delete (*i);
	}

	for (vector<FixedParticle*>::iterator i = fixedParticleList.begin(); i != fixedParticleList.end(); i++) {
		delete (*i);
	}
	
}


/******************
 * Simulation fxns
 ******************/

/** Start the simulation */
void ParticleSystem::startSimulation(float t)
{
    
	// TODO

	// These values are used by the UI ...
	// -ve bake_end_time indicates that simulation
	// is still progressing, and allows the
	// indicator window above the time slider
	// to correctly show the "baked" region
	// in grey.
	bake_end_time = -1;
	bake_start_time = t;
	bake_fps = ModelerApplication::Instance()->GetFps();
	simulate = true;
	dirty = true;

}

/** Stop the simulation */
void ParticleSystem::stopSimulation(float t)
{
    
	// TODO

	// These values are used by the UI
	bake_end_time = t;
	baked = true;
	simulate = false;
	dirty = true;

}

/** Reset the simulation */
void ParticleSystem::resetSimulation(float t)
{
    
	// TODO

	// These values are used by the UI
	simulate = false;
	dirty = true;

}

/** Compute forces and update particles **/
void ParticleSystem::computeForcesAndUpdateParticles(float t)
{
	static double prev_t = 0.0;
	if( simulate) {

		cout << "Computing and updating particles..." << endl;

		// Clear all accumulators
		for (vector<Particle*>::iterator i = particleList.begin(); i != particleList.end(); i++) {
			(*i)->clearAccumulator();
		}

		// Account for all forces
		for (vector<Force*>::iterator i = forceList.begin(); i != forceList.end(); i++) {
			(*i)->accountForces();
		}

		// Update timestep
		for (vector<Particle*>::iterator i = particleList.begin(); i != particleList.end(); i++) {
			updateParticle(*(*i), (t-prev_t));
		}
		bakeParticles(t);

		
	}
	prev_t = t;
}

/** Render particles */
void ParticleSystem::drawParticles(float t)
{
	cout << "Drawing Particles..." << endl;
	if(!simulate && baked && ModelerApplication::Instance()->GetFps() == bake_fps && t <= bake_end_time && t >= bake_start_time ) {
		printf("damn wrong\n");
		vector<Vec3d> v = bakedParticles.find(t)->second;
		for (vector<Vec3d>::iterator i = v.begin(); i != v.end(); i++) {
			glPushMatrix();
			glTranslated((*i)[0], (*i)[1], (*i)[2]);
			drawSphere(0.02);
			glPopMatrix();
		}
		
	} else {
		for (vector<ParticleObject*>::iterator i = objectList.begin(); i != objectList.end(); i++) {
			(*i)->render();
		}
	}
}

/** Adds the current configuration of particles to
  * your data structure for storing baked particles **/
void ParticleSystem::bakeParticles(float t) 
{
	vector<Vec3d> bakedParticlesAtTimeT;
	for (vector<Particle*>::iterator i = particleList.begin(); i != particleList.end(); i++) {
		bakedParticlesAtTimeT.push_back((*i)->getPosition());
	}

	bakedParticles[t] = bakedParticlesAtTimeT;
	// TODO
}

/** Clears out your data structure of baked particles */
void ParticleSystem::clearBaked()
{
	if( !bakedParticles.empty() ) {
		printf("clearing bakes...\n");
		for (map<float, vector<Vec3d> >::iterator i = bakedParticles.begin(); i != bakedParticles.end(); i++) {
			(*i).second.clear();
			vector<Vec3d>().swap((*i).second);
		}
		bakedParticles.clear();
		map<float, vector<Vec3d> >().swap(bakedParticles);
	}
	baked = false;
	// TODO

}

void ParticleSystem::updateParticle(Particle& p, double timeStep) {

	p.setPosition(p.getPosition() + timeStep * p.getVelocity());
	p.setVelocity(p.getVelocity() + timeStep * p.getForce() / p.getMass());

}


/** Spawns a single ball at given position and velocity **/
void ParticleSystem::SpawnBall(Vec3d initialPosition, Vec3d initialVelocity, double mass) 
{
	if(simulate) {	
		// Add particle into particle list
		Particle* p = new Particle(initialPosition, initialVelocity, mass);
		particleList.push_back(p);

		// Add this to the object list
		objectList.push_back(new PointParticleObject(p));

		// Subject this ball to gravity
		gravity->AddParticle(p);

		// Subject the ball to viscous drag
		viscousDrag->AddParticle(p);

		// Add new random force on the ball
		RandomForce* randForce = new RandomForce(1.0, 1.0, 0.1);
		randForce->AddParticle(p);
		forceList.push_back(randForce);
	}

}
/** Spawn a single sparkle at a given position and velocity **/
/** The first point (head) is assumed to be at initialPosition **/
void ParticleSystem::SpawnSparkle(Vec3d initialPosition, Vec3d initialVelocity) {

	if (simulate) {
		// Vector to store all particles involved
		vector<Particle*> sparkleParticleList;

		// Create particles in their right position
		Particle* head = new Particle(initialPosition, initialVelocity, SPARKLES_MASS[0]);
		particleList.push_back(head);
		sparkleParticleList.push_back(head);

		// Add rest of the particles into particle list
		for (int i = 1; i < SPARKLE_NUM_POINTS; i++) {
			Particle* p = new Particle(initialPosition - DISTANCE_BETWEEN_SPARKLES[i-1] * Vec3d(0, 1, 0), initialVelocity, SPARKLES_MASS[i-1]);
			particleList.push_back(p);
			sparkleParticleList.push_back(p);

			// Subject the sparkle to spring forces
			sparkleSpring->addParticlePair(sparkleParticleList[i], sparkleParticleList[i-1]);
		}

		// Create Sparkle object and add to the object list
		objectList.push_back(new SparkleParticleObject(particleList));

		// Subject this sparkle to gravity
		gravity->AddParticle(head);

		// Subject the sparkle to viscous drag
		// viscousDrag->AddParticles(particleList);

		// Add new random force on the ball
		RandomForce* randForce = new RandomForce(5.0, 1.0, 0.1);
		randForce->AddParticle(head);
		forceList.push_back(randForce);
	}
}

/** Spawn a single sparkle at a given position and velocity **/
/** The first point is at initialPosition and contains the base **/
FixedParticle* ParticleSystem::SpawnEar(Vec3d initialPosition, Vec3d initialVelocity) {

	if (simulate) {
		// Vector to store all particles involved
		vector<Particle*> earParticleList;

		// Create base in the right position
		FixedParticle* head = new FixedParticle(initialPosition, initialVelocity, EAR_MASS[0]);
		fixedParticleList.push_back(head);
		earParticleList.push_back(head);

		// Add rest of the particles into particle list
		for (int i = 1; i < EAR_NUM_POINTS; i++) {
			Particle* p = new Particle(initialPosition + DISTANCE_BETWEEN_EAR[i-1] * Vec3d(0, 1, 0), initialVelocity, EAR_MASS[i]);
			particleList.push_back(p);
			earParticleList.push_back(p);

			// Subject the ear to spring forces
			// if (i != 1)
			earSpring->addParticlePair(earParticleList[i], earParticleList[i-1]);
		}

		// Create ear object and add to the object list
		objectList.push_back(new EarParticleObject(earParticleList));

		// Subject this sparkle to gravity
		earStabilizer->AddParticles(earParticleList);

		// Subject the sparkle to viscous drag
		earDrag->AddParticles(earParticleList);

		return head;
	}

	return NULL;
}